# coding=utf-8
import json

import six
from django.db.models import Model
from elasticsearch_dsl import InnerDoc
from rest_framework import serializers
from datetime import datetime, date
from django.utils import timezone
from django.conf import settings
from rest_framework.fields import empty, to_choices_dict, flatten_choices_dict
from rest_framework.serializers import raise_errors_on_nested_writes
from rest_framework.utils import model_meta

from social.exceptions import ModelUpdateForbiddenException, ModelCreateForbiddenException
from social.utils import hashid, json_utils
from social.utils.base64image import convert_base64
import os
from django.db.models.fields.files import ImageFieldFile
from django.db import models
from social.city import display_place_of_code


class IntensifySerializerMethodField(serializers.Field):
    # 自定义字段保存和获取方式
    def __init__(self, method_name=None, save_name=None, **kwargs):
        self.method_name = method_name
        self.save_name = save_name
        kwargs['read_only'] = False
        super(IntensifySerializerMethodField, self).__init__(**kwargs)

    def bind(self, field_name, parent):
        default_method_name = 'get_{field_name}'.format(field_name=field_name)
        default_save_name = 'save_{field_name}'.format(field_name=field_name)
        assert self.method_name != default_method_name, (
                "It is redundant to specify `%s` on SerializerMethodField '%s' in "
                "serializer '%s', because it is the same as the default method name. "
                "Remove the `method_name` argument." %
                (self.method_name, field_name, parent.__class__.__name__)
        )
        assert self.save_name != default_save_name, (
                "It is redundant to specify `%s` on SerializerMethodField '%s' in "
                "serializer '%s', because it is the same as the default method name. "
                "Remove the `method_name` argument." %
                (self.method_name, field_name, parent.__class__.__name__))
        # The method name should default to `get_{field_name}`.
        if self.method_name is None:
            self.method_name = default_method_name
        if self.save_name is None:
            self.save_name = default_save_name
        super(IntensifySerializerMethodField, self).bind(field_name, parent)

    def to_representation(self, value):
        method = getattr(self.parent, self.method_name)
        return method(value)

    def to_internal_value(self, data):
        method = getattr(self.parent, self.save_name)
        return method(data)


class TimestampField(serializers.DateTimeField):

    def __init__(self, format=serializers.empty, input_formats=None, default_timezone=None, *args, **kwargs):
        if kwargs.get("read_only", None) is None:
            kwargs['read_only'] = True
        super(TimestampField, self).__init__(format=serializers.empty, input_formats=None, default_timezone=None,
                                             *args, **kwargs)

    def to_representation(self, obj):
        if isinstance(obj, datetime):
            result = obj.timestamp()
        else:
            result = 0
        return result

    def to_internal_value(self, data):
        if data:
            obj = datetime.fromtimestamp(data, tz=timezone.utc)
            return obj
        else:
            return None


class IntegerFallbackField(serializers.IntegerField):
    # def __init__(self, default=0, **kwargs):
    #     self.default = default
    #     super(IntegerFallbackField, self).__init__(**kwargs)

    def to_representation(self, value):
        try:
            return super(IntegerFallbackField, self).to_representation(value)
        except Exception:
            return 0


class HashidField(serializers.CharField):

    def __init__(self, model, **kwargs):
        self.model_name = model._meta.label
        if kwargs.get('read_only', None) is None:
            kwargs['read_only'] = True

        super(HashidField, self).__init__(**kwargs)

    def to_representation(self, obj):
        return hashid.encode_model_ids(self.model_name, int(obj))

    def to_internal_value(self, data):
        assert self.source != 'id'
        return hashid.decode_model_ids(data, self.model_name)


class ImageUrlField(serializers.CharField):
    def to_representation(self, value):
        if value:
            if isinstance(value, ImageFieldFile):
                return f"{value.url}"
            elif value.startswith("http"):
                return value
            else:
                return os.path.join(settings.IMAGE_HOST, value) + "!"


class ForeignHashidField(serializers.CharField):
    def __init__(self, model, **kwargs):
        self.model_name = model._meta.label
        super(ForeignHashidField, self).__init__(**kwargs)

    def to_representation(self, obj):
        return hashid.encode_model_ids(self.model_name, obj)

    def to_internal_value(self, data):
        assert self.source != 'id'
        return hashid.decode_model_ids(data, self.model_name)


class JSONStringField(serializers.CharField):

    def __init__(self, default_json, **kwargs):
        self.default_json = default_json
        super(JSONStringField, self).__init__(**kwargs)

    def to_representation(self, value):
        if isinstance(value, str):
            return json_utils.to_json(value, self.default_json)
        elif isinstance(value, InnerDoc):
            return value.to_dict()
        return value

    def to_internal_value(self, data):
        if isinstance(data, dict):
            return json.dumps(data)
        return ''


class ArraySplitField(serializers.CharField):
    def __init__(self, split=',', **kwargs):
        self.split = split
        super(ArraySplitField, self).__init__(**kwargs)

    def to_representation(self, value):
        if not value:
            return []
        return value.split(self.split)

    def to_internal_value(self, data):
        if data:
            return self.split.join(data)
        return ''


class ConstellationField(serializers.CharField):
    def to_representation(self, value):
        if isinstance(value, datetime) or isinstance(value, date):
            month = value.month
            day = value.day
            days = (21, 20, 21, 21, 22, 22, 23, 24, 24, 24, 23, 22)
            constellations = (
                "摩羯座", "水瓶座", "双鱼座", "白羊座", "金牛座", "双子座", "巨蟹座", "狮子座", "处女座", "天秤座", "天蝎座", "射手", "摩羯座")
            if day < days[month - 1]:
                return constellations[month - 1]
            else:
                return constellations[month]


class CityNameField(serializers.CharField):
    def to_representation(self, value):
        if value and value.isdigit():
            return display_place_of_code(int(value))


class Base64FileField(serializers.FileField):

    def to_internal_value(self, data):
        data = convert_base64(data, self.source)
        return super(Base64FileField, self).to_internal_value(data)


class Base64ImageField(serializers.ImageField):

    def to_internal_value(self, data):
        data = convert_base64(data, self.source)
        return super(Base64ImageField, self).to_internal_value(data)


class ChoiceDisplayField(serializers.ChoiceField):

    def to_representation(self, value):
        if value is None:
            return value
        return self.choices.get(value, value)

    def to_internal_value(self, data):
        if data == '' and self.allow_blank:
            return ''
        try:
            return self.choice_strings_to_values.get(six.text_type(data)) or (self.choices[data] and data)
        except KeyError:
            self.fail('invalid_choice', input=data)

    def _set_choices(self, choices):
        self.grouped_choices = to_choices_dict(choices)
        self._choices = flatten_choices_dict(self.grouped_choices)

        # Map the string representation of choices to the underlying value.
        # Allows us to deal with eg. integer choices while supporting either
        # integer or string input, but still get the correct datatype out.
        self.choice_strings_to_values = {
            value: key for key, value in self.choices.items()
        }

    def _get_choices(self):
        return self._choices

    choices = property(_get_choices, _set_choices)


class ReadonlyModelSerializer(serializers.ModelSerializer):

    def update(self, instance, validated_data):
        raise ModelUpdateForbiddenException()

    def create(self, validated_data):
        raise ModelCreateForbiddenException()


class DefaultModelSerializer(serializers.ModelSerializer):

    def update(self, instance, validated_data):
        raise_errors_on_nested_writes('update', self, validated_data)
        info = model_meta.get_field_info(instance)
        update_fields = []

        # Simply set each attribute on the instance, and then save it.
        # Note that unlike `.create()` we don't need to treat many-to-many
        # relationships as being a special case. During updates we already
        # have an instance pk for the relationships to be associated with.
        for attr, value in validated_data.items():
            if getattr(instance, attr) != value:
                update_fields.append(attr)
            if attr in info.relations and info.relations[attr].to_many:
                field = getattr(instance, attr)
                field.set(value)
            else:
                setattr(instance, attr, value)
        if update_fields:
            instance.save(update_fields=update_fields)
        return instance


DefaultModelSerializer.serializer_field_mapping[models.AutoField] = HashidField
DefaultModelSerializer.serializer_field_mapping[models.DateTimeField] = TimestampField
DefaultModelSerializer.serializer_field_mapping[models.DateField] = TimestampField
